using System;
using System.Globalization;
using System.Web;
using System.Data.SqlTypes;

namespace Framework
{
    public sealed class DateUtil
	{


        public const string FORMAT_SHORT = "dd-MM-yyyy";
        public const string FORMAT_SHORT2 = "dd/MM/yyyy";
        public const string FORMAT_LONG = "dd-MM-yyyy HH:mm:ss";
		public const string FORMAT_PRECISE = "dd-MM-yyyy HH:mm:ss.fff";

		public const string FORMAT_SQL_SHORT = "yyyy-MM-dd";
		public const string FORMAT_SQL_LONG = "yyyy-MM-dd HH:mm:ss";
		public const string FORMAT_SQL_PRECISE = "yyyy-MM-dd HH:mm:ss.fff";

        public const string FORMAT_SORTABLE = "s";

		public const string FORMAT_YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
		public const string FORMAT_YYYYMMDD = "yyyyMMdd";

        public const string FormatTimeLong = "HH:mm:ss";
	
        //public static readonly DateTime SqlMinDate = new System.DateTime(1753, 01, 01, 12, 0, 0, 0);
        //public static readonly DateTime SqlMaxDate = new System.DateTime(9999, 12, 31, 12, 11, 59, 59);

		// Renamed from SqlMinDate/SqlMaxDate to Min and Max

		/// <summary>
		/// Effective Minimum date supported, compatible with .Net and MS-SQL
		/// </summary>
		public static readonly DateTime MinValue = new System.DateTime(1753, 01, 01, 12, 0, 0, 0);

		/// <summary>
		/// Effective Maximum date supported, compatible with .Net and MS-SQL
		/// </summary>
		public static readonly DateTime MaxValue = new System.DateTime(9999, 12, 31, 12, 11, 59, 59);

		#region FormatDayExtensionDate
        /// <summary>
        /// Show day extension + formated date
        /// </summary>
        /// <param name="date">The date.</param>
        /// <returns></returns>
		public static string FormatDayExtensionDate(DateTime date)
		{
			return FormatDayExtensionDate(date, " MMM");
		}

        /// <summary>
        /// Show day extension + formated date
        /// </summary>
        /// <param name="date">The date.</param>
        /// <param name="format">Date format to follow day extension. Defaults to " MMM"</param>
        /// <returns></returns>
		public static string FormatDayExtensionDate(DateTime date, string format)
		{
			return DayExtension(date.Day) + date.ToString(format);
		}
		#endregion

		#region DayExtension
		/// <summary>
		/// Day Extension Eg 1st, 2nd, 23rd, 15th;
		/// </summary>
		/// <param name="day">Date</param>
		/// <returns></returns>
		public static string DayExtension(DateTime date)
		{
			return DayExtension(date.Day);
		}

		/// <summary>
		/// Day Extension Eg 1st, 2nd, 23rd, 15th;
		/// </summary>
		/// <param name="day">Day of month</param>
		/// <returns></returns>
		public static string DayExtension(int day)
		{
			if (day < 1 || day > 31)
			{
				throw new ArgumentException("Invalid day, cannot build an extension for this day: " + day);
			}

			if (day == 1 || day == 21 || day == 31)
			{
				return day + "st";
			}

			if (day == 2 || day == 22)
			{
				return day + "nd";
			}

			if (day == 3 || day == 23)
			{
				return day + "rd";
			}

			return day + "th";
		}
		#endregion

		#region ToDateTime
		public static DateTime ToDateTime(string dateTime)
		{
			try
			{
				return (Is.EmptyString(dateTime) ? MinValue : DateTime.Parse(dateTime));
			}
			catch
			{
				return DateTime.MinValue;
			}
		}

		public static DateTime ToDateTime(string dateTime, DateTime defaultValue)
		{
			try
			{
				return (Is.EmptyString(dateTime) ? defaultValue : DateTime.Parse(dateTime));
			}
			catch
			{
				return defaultValue;
			}
		}

        public static DateTime ToDateTime(DateTime? dateTime)
        {
            return ToDateTime(dateTime, DateTime.MinValue);
        }
        public static DateTime ToDateTime(DateTime? dateTime, DateTime defaultValue)
        {
            return dateTime == null ? defaultValue : (DateTime)dateTime;
        }
        #endregion

		#region ToUtcDateTime
		public static DateTime ToUtcDateTime(string dateTime)
		{
			try
			{
				return (Is.EmptyString(dateTime) ? DateTime.MinValue : DateTime.Parse(dateTime, null, DateTimeStyles.AdjustToUniversal));
			}
			catch
			{
				return DateTime.MinValue.ToUniversalTime();
			}
		}
		#endregion

		#region ToSqlDateTime
		public static SqlDateTime ToSqlDateTime(string dateTime)
		{
			try
			{
				return Is.EmptyString(dateTime) ? SqlDateTime.Null : new SqlDateTime(DateTime.Parse(dateTime));
			}
			catch (Exception)
			{
				return SqlDateTime.Null;
			}
		}
		#endregion

		#region UtcNowToString
		public static string UtcNowToString()
		{
			return DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
		}
		#endregion

        #region NowAsDateOnly
        /// <summary>
        /// Nows as date only, strips away the time.
        /// </summary>
        /// <returns></returns>
        public static DateTime NowAsDateOnly()
        {
            return new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
        } 
        #endregion

		#region NowToString
		/// <summary>
		/// DateTime.Now as a string, this is a DB friendly format
		/// </summary>
		/// <returns></returns>
		public static string NowToString()
		{
			return DateTime.Now.ToString(FORMAT_SQL_LONG);
		}
		#endregion

		#region NowToStringYYYYMMDDHHMMSS
		public static string NowToStringYYYYMMDDHHMMSS()
		{
			return DateTime.Now.ToString("yyyyMMddHHmmss");
		}
		#endregion

		#region Equal
		/// <summary>
		/// Checks Date and Time to a precision of seconds. Helps if your comparing a C# Generated DateTime to SQL Loaded DateTime which looses precision
		/// </summary>
		/// <param name="dt1"></param>
		/// <param name="dt2"></param>
		/// <returns>true/false</returns>
		public static bool Equal(DateTime dt1, DateTime dt2)
		{
			if (dt1.Year != dt2.Year) return false;
			if (dt1.Month != dt2.Month) return false;
			if (dt1.Day != dt2.Day) return false;
			if (dt1.Hour != dt2.Hour) return false;
			if (dt1.Minute != dt2.Minute) return false;
			if (dt1.Second != dt2.Second) return false;

			return true;
		}
		#endregion

		#region Parse
        public static DateTime Parse(string date)
        {
            return Parse(date, FORMAT_SHORT2, MinValue, OnException.Ignore);
        }

		public static DateTime Parse(string date, string format)
		{
			return Parse(date, format, MinValue, OnException.Ignore);
		}

        public static DateTime Parse(string date, string format, DateTime defaultDate, OnException onException)
        {
            try
            {
                return DateTime.ParseExact(date, format, new CultureInfo("en-AU"));
            }
            catch
            {
                if (onException == OnException.Ignore)
                {
                    return defaultDate;
                }
                throw;
            }
        }
        #endregion

		#region ParseSqlShort
		public static DateTime ParseSqlShort(string date)
		{
			return Parse(date, FORMAT_SQL_SHORT, MinValue, OnException.Ignore);
		}
		public static DateTime ParseSqlShort(string date, DateTime defaultValue)
		{
			return Parse(date, FORMAT_SQL_SHORT, defaultValue, OnException.Ignore);
		}
		public static DateTime ParseSqlShort(string date, OnException onException)
		{
			return Parse(date, FORMAT_SQL_SHORT, MinValue, onException);
		}
		#endregion

        #region Validate
        public static bool Validate(DateTime date)
        {
            return InRange(date, MinValue.AddDays(1), MaxValue);
        }
        #endregion

		#region InRange
		public static bool InRange(DateTime date, DateTime startDate, DateTime endDate)
		{
			return date.Date >= startDate.Date && date.Date <= endDate.Date;
		}
		#endregion

        // *********************************************************************************
        // Difference
        // *********************************************************************************

        // http://yogesh.jagotagroup.com/blog/post/2008/01/31/Calculating-difference-between-two-dates.aspx
        public static DateSpan DateDifference(DateTime date, DateTime dateToCompare)
        {
            // First we calculate total months
            var totalMonths = ((date.Year - dateToCompare.Year) * 12) + date.Month - dateToCompare.Month;

            var days = 0;

            // A month completes on one day before the exact day of the
            // actual date. For example, if starting date is 15-Mar, one 
            // month will complete on 14-Apr regardless of how many days
            // are present in march.
            // So, this is the code to do the same...
            if (date.Day < dateToCompare.Day)
            {
                var day = dateToCompare.Day;
                int month;
                int year;

                // If month is jan, switch to dec
                if (date.Month == 1)
                {
                    month = 12;
                    year = date.Year - 1;
                }
                else
                {
                    month = date.Month - 1;
                    year = date.Year;
                }

                var dateCalculator = new DateTime(year, month, day);

                days = (date - dateCalculator).Days;

                totalMonths--;
            }
            else
            {
                days = date.Day - dateToCompare.Day;
            }

            return new DateSpan
                         {
                             Years = totalMonths/12, 
                             Months = totalMonths%12, 
                             Days = days
                         };
        }

        // *********************************************************************************
		// DateLookups
        // *********************************************************************************

        #region GetFirstDayOfThisWeek
        public static DateTime GetFirstDayOfThisWeek(DateTime date)
        {
            return GetFirstDayOfThisWeek(date, DayOfWeek.Sunday);
        }

        public static DateTime GetFirstDayOfThisWeek(DateTime date, DayOfWeek startOfWeek)
        {
            var diff = date.DayOfWeek - startOfWeek;
            
            if (diff < 0)
            {
                diff += 7;
            }

            return date.AddDays(-1 * diff).Date;
        }
        #endregion

        #region GetLastDayOfThisWeek
        public static DateTime GetLastDayOfThisWeek(DateTime date)
        {
            return GetFirstDayOfThisWeek(date).AddDays(6);// 6 because because we want the end of this week not the beginning of next week
        } 
        #endregion

        #region GetFirstDayOfMonth
        /// <summary>
        /// Gets the first day of the month.
        /// </summary>
        /// <param name="date">The date.</param>
        /// <returns></returns>
        public static DateTime GetFirstDayOfMonth(DateTime date)
		{  
			return new DateTime(date.Year, date.Month, 1);
		}
        #endregion

        #region GetLastDayOfMonth 
        /// <summary>
        /// Gets the last day of month.
        /// </summary>
        /// <param name="date">The date.</param>
        /// <returns></returns>
        public static DateTime GetLastDayOfMonth(DateTime date)
		{
            return GetFirstDayOfMonth(date).AddMonths(1).AddDays(-1);
		}
		#endregion

		#region GetQuarter
		public static DateTime GetQuarterStart( DateTime date )
		{            
			switch(date.Month)
			{
				case 1:
				case 2:
				case 3:
					return new DateTime(date.Year, 1, 1);

				case 4:
				case 5:
				case 6:
					return new DateTime(date.Year, 4, 1);

				case 7:
				case 8:
				case 9:
					return new DateTime(date.Year, 7, 1);
			
				case 10:
				case 11:
				case 12:
					return new DateTime(date.Year, 10, 1);

				default:
					throw new ArgumentException("Invalidate date value", "date");
			}
		}
		public static DateTime GetQuarterEnd( DateTime date )
		{   
			return GetQuarterStart(date.AddMonths(3)).AddDays(-1);
		}
		#endregion

		#region GetLastQuarter
		public static DateTime GetLastQuarterStart(DateTime date)
		{
			return GetQuarterStart(date).AddMonths(-3);
		}
		public static DateTime GetLastQuarterEnd(DateTime date)
		{
			return GetQuarterStart(date).AddDays(-1);
		}
		#endregion

        #region GetFirstDayOfThisYear
        public static DateTime GetFirstDayOfThisYear( DateTime date )
		{
			return new DateTime(date.Year, 1, 1);
		}
        #endregion

        #region GetLastDayOfThisYear
        public static DateTime GetLastDayOfThisYear(DateTime date)
		{
            return GetFirstDayOfThisYear(date).AddYears(1).AddDays(-1);
		}
		#endregion

        // *********************************************************************************
        // Last Week, Last Month, Last Year
        // *********************************************************************************


        #region GetFirstDayOfLastWeek
        public static DateTime GetFirstDayOfLastWeek( DateTime date )
		{
            return GetFirstDayOfThisWeek(date).AddDays(-7); 
		}
        #endregion

        #region GetLastDayOfLastWeek
        public static DateTime GetLastDayOfLastWeek(DateTime date)
		{
            return GetFirstDayOfThisWeek(date).AddDays(-1); 
		}
		#endregion

        #region GetFirstDayOfLastMonth
        public static DateTime GetFirstDayOfLastMonth( DateTime date )
		{
			return GetFirstDayOfMonth(date).AddMonths( -1 );
		}
        #endregion

        #region GetLastDayOfLastMonth
        public static DateTime GetLastDayOfLastMonth( DateTime date )
		{
            return GetFirstDayOfMonth(date).AddDays(-1);
		}
		#endregion

        #region GetFirstDayOfLastYear
        public static DateTime GetFirstDayOfLastYear( DateTime date )
		{
            return GetFirstDayOfThisYear(date).AddYears(-1);
		}
        #endregion

        #region GetLastDayOfLastYear
        public static DateTime GetLastDayOfLastYear( DateTime date )
		{
            return GetFirstDayOfThisYear(date).AddDays(-1);
		}
		#endregion
    }
}
